{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 快速入门\n",
    "\n",
    "本案例通过MindSpore的API来快速实现一个简单的深度学习模型。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 环境准备\n",
    "\n",
    "开发者拿到香橙派开发板后，首先需要进行硬件资源确认、镜像烧录以及CANN和MindSpore版本的升级，才可运行该案例，具体如下：\n",
    "\n",
    "| 香橙派AIpro | 镜像 | CANN Toolkit/Kernels | MindSpore |\n",
    "| :----:| :----: | :----:| :----: |\n",
    "| 8T 8G | Ubuntu | 8.1.RC1 | 2.6.0 |\n",
    "\n",
    "### 镜像烧录\n",
    "\n",
    "运行该案例需要烧录香橙派官网Ubuntu镜像，参考[镜像烧录](https://www.mindspore.cn/tutorials/zh-CN/r2.6.0rc1/orange_pi/environment_setup.html#1-%E9%95%9C%E5%83%8F%E7%83%A7%E5%BD%95%E4%BB%A5windows%E7%B3%BB%E7%BB%9F%E4%B8%BA%E4%BE%8B)章节。\n",
    "\n",
    "### CANN升级\n",
    "\n",
    "参考[CANN升级](https://www.mindspore.cn/tutorials/zh-CN/r2.6.0rc1/orange_pi/environment_setup.html#3-cann%E5%8D%87%E7%BA%A7)章节。\n",
    "\n",
    "### MindSpore升级\n",
    "\n",
    "参考[MindSpore升级](https://www.mindspore.cn/tutorials/zh-CN/r2.6.0rc1/orange_pi/environment_setup.html#4-mindspore%E5%8D%87%E7%BA%A7)章节。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/miniconda3/lib/python3.9/site-packages/numpy/core/getlimits.py:518: UserWarning: The value of the smallest subnormal for <class 'numpy.float64'> type is zero.\n",
      "  setattr(self, word, getattr(machar, word).flat[0])\n",
      "/usr/local/miniconda3/lib/python3.9/site-packages/numpy/core/getlimits.py:89: UserWarning: The value of the smallest subnormal for <class 'numpy.float64'> type is zero.\n",
      "  return self._float_to_str(self.smallest_subnormal)\n",
      "/usr/local/miniconda3/lib/python3.9/site-packages/numpy/core/getlimits.py:518: UserWarning: The value of the smallest subnormal for <class 'numpy.float32'> type is zero.\n",
      "  setattr(self, word, getattr(machar, word).flat[0])\n",
      "/usr/local/miniconda3/lib/python3.9/site-packages/numpy/core/getlimits.py:89: UserWarning: The value of the smallest subnormal for <class 'numpy.float32'> type is zero.\n",
      "  return self._float_to_str(self.smallest_subnormal)\n"
     ]
    }
   ],
   "source": [
    "import mindspore\n",
    "from mindspore import mint\n",
    "from mindspore.nn import Cell, SGD\n",
    "from mindspore.mint import nn\n",
    "from mindspore.dataset import vision, transforms\n",
    "from mindspore.dataset import MnistDataset"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 数据集准备与加载\n",
    "\n",
    "MindSpore提供基于Pipeline的[数据引擎](https://www.mindspore.cn/docs/zh-CN/r2.6.0rc1/design/data_engine.html)，通过[数据集（Dataset）](https://www.mindspore.cn/tutorials/zh-CN/r2.6.0rc1/beginner/dataset.html)实现高效的数据预处理。在本案例中，我们使用Mnist数据集，自动下载完成后，使用`mindspore.dataset`提供的数据变换进行预处理。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: download in /usr/local/miniconda3/lib/python3.9/site-packages (0.3.5)\n",
      "Requirement already satisfied: tqdm in /usr/local/miniconda3/lib/python3.9/site-packages (from download) (4.64.1)\n",
      "Requirement already satisfied: six in /usr/local/miniconda3/lib/python3.9/site-packages (from download) (1.16.0)\n",
      "Requirement already satisfied: requests in /usr/local/miniconda3/lib/python3.9/site-packages (from download) (2.31.0)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->download) (2.0.4)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->download) (2023.11.17)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->download) (1.26.14)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/miniconda3/lib/python3.9/site-packages (from requests->download) (3.4)\n",
      "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
      "\u001b[0m"
     ]
    }
   ],
   "source": [
    "# install download\n",
    "\n",
    "!pip install download"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/MNIST_Data.zip (10.3 MB)\n",
      "\n",
      "file_sizes: 100%|██████████████████████████| 10.8M/10.8M [00:00<00:00, 12.6MB/s]\n",
      "Extracting zip file...\n",
      "Successfully downloaded / unzipped to ./\n"
     ]
    }
   ],
   "source": [
    "# Download data from open datasets\n",
    "from download import download\n",
    "\n",
    "url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/\" \\\n",
    "      \"notebook/datasets/MNIST_Data.zip\"\n",
    "path = download(url, \"./\", kind=\"zip\", replace=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "MNIST数据集目录结构如下：\n",
    "\n",
    "```text\n",
    "MNIST_Data\n",
    "└── train\n",
    "    ├── train-images-idx3-ubyte (60000个训练图片)\n",
    "    ├── train-labels-idx1-ubyte (60000个训练标签)\n",
    "└── test\n",
    "    ├── t10k-images-idx3-ubyte (10000个测试图片)\n",
    "    ├── t10k-labels-idx1-ubyte (10000个测试标签)\n",
    "\n",
    "```\n",
    "\n",
    "数据下载完成后，获得数据集对象。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_dataset = MnistDataset('MNIST_Data/train')\n",
    "test_dataset = MnistDataset('MNIST_Data/test')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "打印数据集中包含的数据列名，用于dataset的预处理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['image', 'label']\n"
     ]
    }
   ],
   "source": [
    "print(train_dataset.get_col_names())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "MindSpore的dataset使用数据处理流水线（Data Processing Pipeline），需指定map、batch、shuffle等操作。这里我们使用map对图像数据及标签进行变换处理，将输入的图像缩放为1/255，根据均值0.1307和标准差值0.3081进行归一化处理，然后将处理好的数据集打包为大小为64的batch。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def datapipe(dataset, batch_size):\n",
    "    image_transforms = [\n",
    "        vision.Rescale(1.0 / 255.0, 0),\n",
    "        vision.Normalize(mean=(0.1307,), std=(0.3081,)),\n",
    "        vision.HWC2CHW(),\n",
    "        transforms.TypeCast(mindspore.float16)\n",
    "    ]\n",
    "    label_transform = transforms.TypeCast(mindspore.int32)\n",
    "\n",
    "    dataset = dataset.map(image_transforms, 'image')\n",
    "    dataset = dataset.map(label_transform, 'label')\n",
    "    dataset = dataset.batch(batch_size)\n",
    "    return dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Map vision transforms and batch dataset\n",
    "train_dataset = datapipe(train_dataset, 64)\n",
    "test_dataset = datapipe(test_dataset, 64)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可使用[create_tuple_iterator](https://www.mindspore.cn/docs/zh-CN/r2.6.0rc1/api_python/dataset/dataset_method/iterator/mindspore.dataset.Dataset.create_tuple_iterator.html) 或[create_dict_iterator](https://www.mindspore.cn/docs/zh-CN/r2.6.0rc1/api_python/dataset/dataset_method/iterator/mindspore.dataset.Dataset.create_dict_iterator.html)对数据集进行迭代访问，查看数据和标签的shape和datatype。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape of image [N, C, H, W]: (64, 1, 28, 28) Float16\n",
      "Shape of label: (64,) Int32\n"
     ]
    }
   ],
   "source": [
    "for image, label in test_dataset.create_tuple_iterator():\n",
    "    print(f\"Shape of image [N, C, H, W]: {image.shape} {image.dtype}\")\n",
    "    print(f\"Shape of label: {label.shape} {label.dtype}\")\n",
    "    break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape of image [N, C, H, W]: (64, 1, 28, 28) Float16\n",
      "Shape of label: (64,) Int32\n"
     ]
    }
   ],
   "source": [
    "for data in test_dataset.create_dict_iterator():\n",
    "    print(f\"Shape of image [N, C, H, W]: {data['image'].shape} {data['image'].dtype}\")\n",
    "    print(f\"Shape of label: {data['label'].shape} {data['label'].dtype}\")\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型构建"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Network(\n",
      "  (dense1): Linear(input_features=784, output_features=512, has_bias=True)\n",
      "  (dense2): Linear(input_features=512, output_features=512, has_bias=True)\n",
      "  (dense3): Linear(input_features=512, output_features=10, has_bias=True)\n",
      "  (relu): ReLU()\n",
      ")\n"
     ]
    }
   ],
   "source": [
    "# Define model\n",
    "class Network(Cell):\n",
    "    def __init__(self):\n",
    "        super().__init__()\n",
    "        self.flatten = mint.flatten\n",
    "        self.dense1 = nn.Linear(28*28, 512, dtype=mindspore.float16)\n",
    "        self.dense2 = nn.Linear(512, 512, dtype=mindspore.float16)\n",
    "        self.dense3 = nn.Linear(512, 10, dtype=mindspore.float16)\n",
    "        self.relu = nn.ReLU()\n",
    "\n",
    "    def construct(self, x):\n",
    "        x = self.flatten(x, start_dim=1)\n",
    "        x = self.dense1(x)\n",
    "        x = self.relu(x)\n",
    "        x = self.dense2(x)\n",
    "        x = self.relu(x)\n",
    "        logits = self.dense3(x)\n",
    "        return logits\n",
    "\n",
    "model = Network()\n",
    "print(model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型训练"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在模型训练中，一个完整的训练过程（step）需要实现以下三步：\n",
    "\n",
    "1. **正向计算**：模型预测结果（logits），并与正确标签（label）求预测损失（loss）。\n",
    "2. **反向传播**：利用自动微分机制，自动求模型参数（parameters）对于loss的梯度（gradients）。\n",
    "3. **参数优化**：将梯度更新到参数上。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "MindSpore使用函数式自动微分机制，因此针对上述步骤需要实现：\n",
    "\n",
    "1. 定义正向计算函数。\n",
    "2. 使用[value_and_grad](https://www.mindspore.cn/docs/zh-CN/r2.6.0rc1/api_python/mindspore/mindspore.value_and_grad.html)通过函数变换获得梯度计算函数。\n",
    "3. 定义训练函数，使用[set_train](https://www.mindspore.cn/docs/zh-CN/r2.6.0rc1/api_python/nn/mindspore.nn.Cell.html#mindspore.nn.Cell.set_train)设置为训练模式，执行正向计算、反向传播和参数优化。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Instantiate loss function and optimizer\n",
    "loss_fn = nn.CrossEntropyLoss()\n",
    "optimizer = SGD(model.trainable_params(), 1e-2)\n",
    "\n",
    "# 1. Define forward function\n",
    "def forward_fn(data, label):\n",
    "    logits = model(data)\n",
    "    loss = loss_fn(logits, label)\n",
    "    return loss, logits\n",
    "\n",
    "# 2. Get gradient function\n",
    "grad_fn = mindspore.value_and_grad(forward_fn, None, optimizer.parameters, has_aux=True)\n",
    "\n",
    "# 3. Define function of one-step training\n",
    "def train_step(data, label):\n",
    "    (loss, _), grads = grad_fn(data, label)\n",
    "    optimizer(grads)\n",
    "    return loss\n",
    "\n",
    "def train(model, dataset):\n",
    "    size = dataset.get_dataset_size()\n",
    "    model.set_train()\n",
    "    for batch, (data, label) in enumerate(dataset.create_tuple_iterator()):\n",
    "        loss = train_step(data, label)\n",
    "\n",
    "        if batch % 100 == 0:\n",
    "            loss, current = loss.asnumpy(), batch\n",
    "            print(f\"loss: {loss:>7f}  [{current:>3d}/{size:>3d}]\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "除训练外，我们定义测试函数，用来评估模型的性能。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test(model, dataset, loss_fn):\n",
    "    num_batches = dataset.get_dataset_size()\n",
    "    model.set_train(False)\n",
    "    total, test_loss, correct = 0, 0, 0\n",
    "    for data, label in dataset.create_tuple_iterator():\n",
    "        pred = model(data)\n",
    "        total += len(data)\n",
    "        test_loss += loss_fn(pred, label).asnumpy()\n",
    "        correct += (mint.argmax(pred, dim=1) == label).asnumpy().sum()\n",
    "    test_loss /= num_batches\n",
    "    correct /= total\n",
    "    print(f\"Test: \\n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \\n\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "训练过程需多次迭代数据集，一次完整的迭代称为一轮（epoch）。在每一轮，遍历训练集进行训练，结束后使用测试集进行预测。打印每一轮的loss值和预测准确率（Accuracy），可以看到loss在不断下降，Accuracy在不断提高。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1\n",
      "-------------------------------\n",
      ".loss: 2.291016  [  0/938]\n",
      "loss: 1.763672  [100/938]\n",
      "loss: 0.964844  [200/938]\n",
      "loss: 0.760742  [300/938]\n",
      "loss: 0.473633  [400/938]\n",
      "loss: 0.449951  [500/938]\n",
      "loss: 0.392822  [600/938]\n",
      "loss: 0.382324  [700/938]\n",
      "loss: 0.485352  [800/938]\n",
      "loss: 0.181885  [900/938]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[WARNING] MD(5139,e7ff1078f120,python):2025-07-22-11:36:29.654.799 [mindspore/ccsrc/minddata/dataset/engine/datasetops/batch_op.cc:146] operator()] Memory consumption is more than 81.9409%, which may cause OOM. Please reduce num_parallel_workers size / optimize 'per_batch_map' function / other python data preprocess function to reduce memory usage.\n",
      "[WARNING] MD(5139,e7ff127cf120,python):2025-07-22-11:36:33.235.978 [mindspore/ccsrc/minddata/dataset/engine/datasetops/batch_op.cc:146] operator()] Memory consumption is more than 81.6789%, which may cause OOM. Please reduce num_parallel_workers size / optimize 'per_batch_map' function / other python data preprocess function to reduce memory usage.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test: \n",
      " Accuracy: 91.0%, Avg loss: 0.318458 \n",
      "\n",
      "Epoch 2\n",
      "-------------------------------\n",
      "loss: 0.329834  [  0/938]\n",
      "loss: 0.429443  [100/938]\n",
      "loss: 0.292236  [200/938]\n",
      "loss: 0.353760  [300/938]\n",
      "loss: 0.437988  [400/938]\n",
      "loss: 0.250000  [500/938]\n",
      "loss: 0.245605  [600/938]\n",
      "loss: 0.257080  [700/938]\n",
      "loss: 0.243530  [800/938]\n",
      "loss: 0.173096  [900/938]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[WARNING] MD(5139,e7ff127cf120,python):2025-07-22-11:37:03.800.668 [mindspore/ccsrc/minddata/dataset/engine/datasetops/batch_op.cc:146] operator()] Memory consumption is more than 80.8599%, which may cause OOM. Please reduce num_parallel_workers size / optimize 'per_batch_map' function / other python data preprocess function to reduce memory usage.\n",
      "[WARNING] MD(5139,e7ff127cf120,python):2025-07-22-11:37:06.621.141 [mindspore/ccsrc/minddata/dataset/engine/datasetops/batch_op.cc:146] operator()] Memory consumption is more than 80.2724%, which may cause OOM. Please reduce num_parallel_workers size / optimize 'per_batch_map' function / other python data preprocess function to reduce memory usage.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test: \n",
      " Accuracy: 92.7%, Avg loss: 0.255700 \n",
      "\n",
      "Epoch 3\n",
      "-------------------------------\n",
      "loss: 0.205933  [  0/938]\n",
      "loss: 0.176758  [100/938]\n",
      "loss: 0.459473  [200/938]\n",
      "loss: 0.161255  [300/938]\n",
      "loss: 0.222412  [400/938]\n",
      "loss: 0.169067  [500/938]\n",
      "loss: 0.314453  [600/938]\n",
      "loss: 0.080139  [700/938]\n",
      "loss: 0.415771  [800/938]\n",
      "loss: 0.241455  [900/938]\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[WARNING] MD(5139,e7fedd40f120,python):2025-07-22-11:37:29.669.746 [mindspore/ccsrc/minddata/dataset/engine/datasetops/batch_op.cc:146] operator()] Memory consumption is more than 80.8526%, which may cause OOM. Please reduce num_parallel_workers size / optimize 'per_batch_map' function / other python data preprocess function to reduce memory usage.\n",
      "[WARNING] MD(5139,e7ff127cf120,python):2025-07-22-11:37:32.509.860 [mindspore/ccsrc/minddata/dataset/engine/datasetops/batch_op.cc:146] operator()] Memory consumption is more than 80.259%, which may cause OOM. Please reduce num_parallel_workers size / optimize 'per_batch_map' function / other python data preprocess function to reduce memory usage.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test: \n",
      " Accuracy: 93.6%, Avg loss: 0.212445 \n",
      "\n",
      "Done!\n"
     ]
    }
   ],
   "source": [
    "epochs = 3\n",
    "for t in range(epochs):\n",
    "    print(f\"Epoch {t+1}\\n-------------------------------\")\n",
    "    train(model, train_dataset)\n",
    "    test(model, test_dataset, loss_fn)\n",
    "print(\"Done!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 保存模型\n",
    "\n",
    "模型训练完成后，需要将其参数进行保存。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Saved Model to model.ckpt\n"
     ]
    }
   ],
   "source": [
    "# Save checkpoint\n",
    "mindspore.save_checkpoint(model, \"model.ckpt\")\n",
    "print(\"Saved Model to model.ckpt\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 权重加载"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "加载保存的权重分为两步：\n",
    "\n",
    "1. 重新实例化模型对象，构造模型。\n",
    "2. 加载模型参数，并将其加载至模型上。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[]\n"
     ]
    }
   ],
   "source": [
    "# Instantiate a random initialized model\n",
    "model = Network()\n",
    "# Load checkpoint and load parameter to model\n",
    "param_dict = mindspore.load_checkpoint(\"model.ckpt\")\n",
    "param_not_load, _ = mindspore.load_param_into_net(model, param_dict)\n",
    "print(param_not_load)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> `param_not_load`是未被加载的参数列表，为空时代表所有参数均加载成功。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 模型推理\n",
    "\n",
    "加载后的模型可以直接用于预测推理。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Predicted: \"[2 1 1 5 7 8]\", Actual: \"[2 1 1 5 7 8]\"\n"
     ]
    },
    {
     "data": {
      "image/png": "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b791Mg0TBcPByAAAAMpRDAAAoBzFAAAAylEMAACgHMUAAADKJexqggceeMCbFxYWOllubm5Y97rrrrucrF69ek7mmyn74YcfOln79u299ykpKalB6wAgtl5++WUnGzp0aAxaYvm2hh8zZoz32Ly8vGg3JyEwMgAAgHIUAwAAKEcxAACAchQDAAAol7ATCLt37+7NfdsMh+u3335zsj//+c9Ods455zhZly5dnKxfv37e+7z//vvVbxwA1JLLLrvMmw8aNKjG1/z111+9+X333edkN954o5OdcsopTla/fn0nu/POO733mTt3rpPt3r3be2wyY2QAAADlKAYAAFCOYgAAAOUoBgAAUC5hJxCuXbvWm/t2G9y0aZOTvfPOO07WsmXLwPcvLS11sqA7CI4bN86bM4EQ8W7kyJHevHXr1k6Wmur+rrF06dKItwm1Z/To0d48LS2txte86qqrvPnbb7/tZPPnz3eyOXPmONmAAQOcLDs723ufM88808k++eQT77HJjJEBAACUoxgAAEA5igEAAJSjGAAAQLmEnUD41ltvefO+ffs62YMPPuhkvrezDLUzYDi2bNniZEwURCLo1KmTk02bNs17rDHGycrKypwsGjuEIjqysrKcrGPHjt5jU1JSnMy3s6Cv//gmCobSoEEDJ+vQoUOg9nz33Xfea2qcLOjDyAAAAMpRDAAAoBzFAAAAylEMAACgXMJOIJw9e7Y39+16NmzYMCfr1q1bWPffv3+/k7300ktOds899wQ6F4g3TZo0CZSF4ptA9uOPP4bVJtSes88+28l8kwpF/BNI582b52QLFixwMt/P51DXnDRpkpOdfPLJTvb999872fTp0733gcXIAAAAylEMAACgHMUAAADKUQwAAKAcxQAAAMol7GqCUF544YVAme+9rX2zUkMpKipystWrVwc+H0h2ixcvdrLly5fHoCWoiZ07d4Z1/rnnnutk119/vZPdfffd3vN9qwl8fvvtNydbv369k4VagQaLkQEAAJSjGAAAQDmKAQAAlKMYAABAuaSbQBjU5s2bA2WAVieccIKT+d4nPpRPP/00ks1BLatXr56T7dmzx3tsRkaGk51yyimBsurwTRZ86qmnnOzOO+8M6z4aMTIAAIByFAMAAChHMQAAgHIUAwAAKKd2AiGAY/PtFhd0VzgRkffeey+SzUEtKygocLLnn3/ee+yYMWOcrH79+mHd/9FHH3Uy326yvt0GUX2MDAAAoBzFAAAAylEMAACgHMUAAADKpZiAM4Kqs/MYEEp1JqBFCn23Zl599VUnu/TSS73Hzp8/38muvvrqiLcplui7SFRB+i4jAwAAKEcxAACAchQDAAAoRzEAAIByFAMAACjHdsQAvFasWOFkoVYTTJ06NdrNARBFjAwAAKAcxQAAAMpRDAAAoBzFAAAAyrEdMWoVW7oiUdF3kajYjhgAAFSJYgAAAOUoBgAAUI5iAAAA5QJPIAQAAMmJkQEAAJSjGAAAQDmKAQAAlKMYAABAOYoBAACUoxgAAEA5igEAAJSjGAAAQDmKAQAAlPt/MBzP/b6vDtAAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<Figure size 640x480 with 6 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "model.set_train(False)\n",
    "for data, label in test_dataset:\n",
    "    pred = model(data)\n",
    "    predicted = mint.argmax(pred, dim=1)\n",
    "    print(f'Predicted: \"{predicted[:6]}\", Actual: \"{label[:6]}\"')\n",
    "\n",
    "    # 显示数字及数字的预测值\n",
    "    plt.figure()\n",
    "    for i in range(6):\n",
    "        plt.subplot(2, 3, i + 1)\n",
    "        # 若预测正确，显示为蓝色；若预测错误，显示为红色\n",
    "        color = 'blue' if predicted[i] == label[i] else 'red'\n",
    "        plt.title('Predicted:{}'.format(predicted[i]), color=color)\n",
    "        plt.imshow(data.asnumpy()[i][0], interpolation=\"None\", cmap=\"gray\")\n",
    "        plt.axis('off')\n",
    "    plt.show()\n",
    "    break"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "本案例已同步上线[GitHub仓](https://github.com/mindspore-courses/orange-pi-mindspore/tree/master/Online/01-quick%20start)，更多案例可参考该仓库。\n",
    "\n",
    "本案例运行所需环境：\n",
    "\n",
    "| 香橙派AIpro | 镜像 | CANN Toolkit/Kernels | MindSpore |\n",
    "| :----:| :----: | :----:| :----: |\n",
    "| 8T 8G | Ubuntu | 8.1.RC1 | 2.6.0 |"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.2"
  },
  "vscode": {
   "interpreter": {
    "hash": "8c9da313289c39257cb28b126d2dadd33153d4da4d524f730c81a4aaccbd2ca7"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
